﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Xml.Linq;
using System.Xml.XPath;
using System.IO;
using System.IO.IsolatedStorage;

namespace PowerTriggersWcf
{
    [DataContract(Namespace = "http://PWTWcf")]
    public enum HistorySeverityLevels
    {
        [EnumMember]
        Informational = 1,
        [EnumMember]
        Success = 2,
        [EnumMember]
        Warning = 3,
        [EnumMember]
        Error = 4,
    }

    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
    [ServiceContract(Namespace = "http://PWTWcf")]
    public interface IPowerTriggersWcf
    {
        [OperationContract]
        ConfigData GetData();

        [OperationContract]
        bool PutData(ConfigData composite);

        //[OperationContract]
        //void AddHistory(HistoryHeader historyHeader, HistorySeverityLevels severity, string historyText);

        [OperationContract(IsOneWay = true)]
        void RunTest(string testType, Task[] tasks);

        [OperationContract]
        Task[] GetAllAvailableTasks(TaskTypes taskType);

        [OperationContract]
        Task CreateTask(TaskTypes taskType
                        , string description
                        , string ID
                        , string status
                        , string category);

        [OperationContract]
        bool AuthenticateUser(ref string username, string password, out string errorMsg);
    }

    [DataContract]
    public class ConfigData
    {
        private const string filePath = "PowerTriggersConfigDataFile";

        private static ConfigData defaultData;
        private static object staticLockObject = new object();

        private object privateLockObject;
        private Task[] suspendTasks;
        private Task[] resumeTasks;

        internal ConfigData()
        {
            Initialise();
        }

        private void Initialise()
        {
            privateLockObject = new object();
        }

        [OnDeserializing()]
        private void OnDeserializing(StreamingContext context)
        {
            Initialise();
        }

        internal ConfigData Clone()
        {
            ConfigData clone = new ConfigData();

            clone.suspendTasks = this.suspendTasks.Select(t => t.Clone()).ToArray();
            clone.resumeTasks = this.resumeTasks.Select(t => t.Clone()).ToArray();

            return clone;
        }

        [DataMember]
        public Task[] SuspendTasks
        {
            get
            {
                lock (privateLockObject) { return this.suspendTasks.ToArray(); }
            }
            set
            {
                lock (privateLockObject) { this.suspendTasks = value; }
            }
        }

        [DataMember]
        public Task[] ResumeTasks
        {
            get
            {
                lock (privateLockObject) { return this.resumeTasks.ToArray(); }
            }
            set
            {
                lock (privateLockObject) { this.resumeTasks = value; }
            }
        }

        [DataMember]
        public IDictionary<TaskTypes, IList<TaskProperty>> DefaultTaskProperties
        {
            get
            {
                lock (privateLockObject) { return Task.DefaultProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); }
            }
            private set
            {
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        private void Load()
        {
            lock (privateLockObject)
            {
                XDocument xDoc;
                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read))
                {
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        string text = reader.ReadToEnd();
                        if (string.IsNullOrEmpty(text) || text.StartsWith("\0"))
                        {
                            xDoc = new XDocument();
                        }
                        else
                        {
                            xDoc = XDocument.Parse(text, LoadOptions.None);
                        }
                    }
                }

                List<XElement> suspendActions = xDoc.XPathSelectElements("//Root//Suspend//Task").ToList();
                List<XElement> resumeActions = xDoc.XPathSelectElements("//Root//Resume//Task").ToList();

                this.suspendTasks = DeserializeActions(suspendActions);
                this.resumeTasks = DeserializeActions(resumeActions);
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        internal void Save()
        {
            lock (privateLockObject)
            {
                IList<XElement> suspendActions = SerializeActions(this.suspendTasks);
                IList<XElement> resumeActions = SerializeActions(this.resumeTasks);

                XElement root = new XElement("Root");
                root.Add(new XElement("Suspend", suspendActions));
                root.Add(new XElement("Resume", resumeActions));

                XDocument xDoc = new XDocument(root);

                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Truncate, FileAccess.Write))
                {
                    using (StreamWriter writer = new StreamWriter(stream))
                    {
                        writer.Write(xDoc.ToString());
                    }
                }

                defaultData = this;
            }
        }

        internal static bool IsDefaultDataLoaded
        {
            get
            {
                lock (staticLockObject)
                {
                    return defaultData != null;
                }
            }
        }

        internal static ConfigData Default
        {
            get
            {
                lock (staticLockObject)
                {
                    if (defaultData == null)
                    {
                        ConfigData data = new ConfigData();
                        data.Load();
                        defaultData = data;
                    }
                }

                return defaultData;
            }
        }

        private static Task[] DeserializeActions(List<XElement> elements)
        {
            IList<Task> tasks = new List<Task>(elements.Count);

            foreach (var element in elements)
            {
                Task task = Task.CreateTask(element);
                if (task != null)
                {
                    tasks.Add(task);
                }
            }

            return tasks.ToArray();
        }

        private static IList<XElement> SerializeActions(Task[] tasks)
        {
            IList<XElement> returnValue = new List<XElement>(tasks.Length);

            foreach (Task task in tasks.OrderBy(t => t.Sequence))
            {
                XElement xElement = task.Serialize();
                returnValue.Add(xElement);
            }

            return returnValue;
        }
    }

    [DataContract]
    public class HistoryData
    {
        private const string filePath = "PowerTriggersHistoryDataFile";

        private static HistoryData defaultData;
        private static object staticLockObject = new object();

        private object privateLockObject;
        private SortedList<string, HistoryHeader> history;
        private bool historySavesPending;

        internal HistoryData()
        {
            Initialise();
        }

        private void Initialise()
        {
            privateLockObject = new object();
        }

        [OnDeserializing()]
        private void OnDeserializing(StreamingContext context)
        {
            Initialise();
        }

        [DataMember]
        public HistoryHeader[] History
        {
            get
            {
                lock (privateLockObject) { return this.history.Values.ToArray(); }
            }
            private set
            {
                lock (privateLockObject)
                {
                    this.history = new SortedList<string, HistoryHeader>(value.ToDictionary(h => h.Key, history => history));
                }
            }
        }

        internal HistoryData Clone()
        {
            HistoryData clone = new HistoryData();

            clone.History = this.History.Select(h => h.Clone()).ToArray();

            return clone;
        }

        internal void AddHistory(HistoryHeader historyHeader, bool saveData)
        {
            lock (privateLockObject)
            {
                if (Constants.MaxHistoryLines > 0)
                {
                    while (this.history.Count >= Constants.MaxHistoryLines)
                    {
                        this.history.RemoveAt(0);
                    }
                }

                this.history.Add(historyHeader.Key, historyHeader);
            }

            this.Save(saveData);
        }

        internal void Save(bool saveData)
        {
            lock (privateLockObject)
            {
                if (saveData)
                {
                    this.Save();
                }
                else
                {
                    historySavesPending = true;
                }
            }

            Service.RaiseHistoryChangedEvent(this);
        }

        internal void SavePendingHistory()
        {
            lock (privateLockObject)
            {
                if (historySavesPending)
                {
                    this.Save();
                }
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        private void Load()
        {
            lock (privateLockObject)
            {
                historySavesPending = false;

                XDocument xDoc;
                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read))
                {
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        string text = reader.ReadToEnd();
                        if (string.IsNullOrEmpty(text) || text.StartsWith("\0"))
                        {
                            xDoc = new XDocument();
                        }
                        else
                        {
                            xDoc = XDocument.Parse(text, LoadOptions.None);
                        }
                    }
                }

                List<XElement> history = xDoc.XPathSelectElements("//Root//History//Line").ToList();

                this.history = DeserializeHistory(history);
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        internal void Save()
        {
            lock (privateLockObject)
            {
                historySavesPending = false;

                IList<XElement> history = SerializeActions(this.history, "Line");

                XElement root = new XElement("Root");
                root.Add(new XElement("History", history));

                XDocument xDoc = new XDocument(root);

                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Truncate, FileAccess.Write))
                {
                    using (StreamWriter writer = new StreamWriter(stream))
                    {
                        writer.Write(xDoc.ToString());
                    }
                }

                defaultData = this;
            }
        }

        internal static bool IsDefaultDataLoaded
        {
            get
            {
                lock (staticLockObject)
                {
                    return defaultData != null;
                }
            }
        }

        internal static HistoryData Default
        {
            get
            {
                lock (staticLockObject)
                {
                    if (defaultData == null)
                    {
                        HistoryData data = new HistoryData();
                        data.Load();
                        defaultData = data;
                    }
                }

                return defaultData;
            }
        }

        private static SortedList<string, HistoryHeader> DeserializeHistory(List<XElement> elements)
        {
            SortedList<string, HistoryHeader> items = new SortedList<string, HistoryHeader>(elements.Count);

            foreach (var element in elements)
            {
                HistoryHeader item = new HistoryHeader(element);
                items.Add(item.Key, item);
            }

            return items;
        }

        private static IList<XElement> SerializeActions(SortedList<string, HistoryHeader> items, string elementName)
        {
            IList<XElement> returnValue = new List<XElement>(items.Count);

            foreach (HistoryHeader item in items.Values)
            {
                XElement xElement = item.Serialize(elementName);
                returnValue.Add(xElement);
            }

            return returnValue;
        }
    }

    [DataContract]
    public class HistoryHeader
    {
        private object lockObject;
        private SortedList<string, HistoryDetail> details;

        internal HistoryHeader()
        {
            Initialise();
        }

        private void Initialise()
        {
            lockObject = new object();
        }

        [OnDeserializing()]
        private void OnDeserializing(StreamingContext context)
        {
            Initialise();
        }

        internal HistoryHeader(HistorySeverityLevels severity, DateTime dateTime, string text) 
            : this()
        {
            details = new SortedList<string, HistoryDetail>();
            Severity = severity;
            DateTime = dateTime;
            Text = text;
        }

        internal HistoryHeader(XElement xElement)
            : this()
        {
            XElement subElement;

            subElement = xElement.Descendants("Severity").FirstOrDefault();
            this.Severity = subElement == null ? HistorySeverityLevels.Informational : (HistorySeverityLevels)Enum.Parse(typeof(HistorySeverityLevels), subElement.Value, true);

            subElement = xElement.Descendants("DateTime").FirstOrDefault();
            this.DateTime = subElement == null ? DateTime.MinValue.ToUniversalTime() : DateTime.FromBinary(long.Parse(subElement.Value));

            subElement = xElement.Descendants("Text").FirstOrDefault();
            this.Text = subElement == null ? string.Empty : subElement.Value;

            List<XElement> xDetails = xElement.Descendants("Detail").ToList();
            details = new SortedList<string, HistoryDetail>(xDetails.Count);
            foreach (XElement xDetail in xDetails)
            {
                HistoryDetail detail = new HistoryDetail(xDetail);
                details.Add(detail.Key, detail);
            }
        }

        internal HistoryHeader Clone()
        {
            HistoryHeader clone = new HistoryHeader(this.Severity, this.DateTime, this.Text);

            clone.details = new SortedList<string, HistoryDetail>(this.details.ToDictionary(d => d.Key, d => d.Value.Clone()));

            return clone;
        }

        [DataMember]
        public HistorySeverityLevels Severity { get; private set; }

        [DataMember]
        public DateTime DateTime { get; private set; }

        [DataMember]
        public string Text { get; private set; }

        [DataMember]
        public HistoryDetail[] Details
        {
            get
            {
                HistoryDetail[] returnArray;

                lock (lockObject)
                {
                    returnArray = this.details.Values.ToArray();
                }

                return returnArray;
            }
            private set
            {
                lock (lockObject)
                {
                    this.details = new SortedList<string, HistoryDetail>(value.ToDictionary(h => h.Key, h => h));
                }
            }
        }

        internal string Key
        {
            get
            {
                return string.Format("{0}|{1}", this.DateTime.ToString("O"), this.Text);
            }
        }

        internal HistoryDetail AddDetail(HistorySeverityLevels severity, string text)
        {
            HistoryDetail detail = new HistoryDetail(severity, DateTime.UtcNow, text);

            lock (lockObject)
            {
                this.details.Add(detail.Key, detail);
            }

            return detail;
        }

        internal XElement Serialize(string elementName)
        {
            XElement returnElement = new XElement(elementName);

            returnElement.Add(new XElement("Severity", this.Severity.ToString()));
            returnElement.Add(new XElement("DateTime", this.DateTime.ToBinary().ToString()));
            returnElement.Add(new XElement("Text", this.Text));

            lock (this.details)
            {
                if (this.details.Count > 0)
                {
                    XElement topNode = new XElement("Details");

                    foreach (HistoryDetail detail in details.Values)
                    {
                        topNode.Add(detail.Serialize("Detail"));
                    }

                    returnElement.Add(topNode);
                }
            }

            return returnElement;
        }

        public override string ToString()
        {
            return string.Format("H {0} {1}: {2}", this.Severity.ToString().Substring(0, 1), this.DateTime.ToLocalTime().ToString(), this.Text);
        }
    }

    [DataContract]
    public class HistoryDetail
    {
        private HistoryDetail() { }

        internal HistoryDetail(HistorySeverityLevels severity, DateTime dateTime, string text)
        {
            Severity = severity;
            DateTime = dateTime;
            Text = text;
        }

        internal HistoryDetail(XElement xElement)
        {
            XElement subElement;

            subElement = xElement.Descendants("Severity").FirstOrDefault();
            this.Severity = subElement == null ? HistorySeverityLevels.Informational : (HistorySeverityLevels)Enum.Parse(typeof(HistorySeverityLevels), subElement.Value, true);

            subElement = xElement.Descendants("DateTime").FirstOrDefault();
            this.DateTime = subElement == null ? DateTime.MinValue.ToUniversalTime() : DateTime.FromBinary(long.Parse(subElement.Value));

            subElement = xElement.Descendants("Text").FirstOrDefault();
            this.Text = subElement == null ? string.Empty : subElement.Value;
        }

        internal HistoryDetail Clone()
        {
            return new HistoryDetail(this.Severity, this.DateTime, this.Text);
        }

        [DataMember]
        public HistorySeverityLevels Severity { get; private set; }

        [DataMember]
        public DateTime DateTime { get; private set; }

        [DataMember]
        public string Text { get; private set; }

        internal string Key
        {
            get
            {
                return string.Format("{0}|{1}", this.DateTime.ToString("O"), this.Text);
            }
        }

        internal XElement Serialize(string elementName)
        {
            XElement returnElement = new XElement(elementName);

            returnElement.Add(new XElement("Severity", this.Severity.ToString()));
            returnElement.Add(new XElement("DateTime", this.DateTime.ToBinary().ToString()));
            returnElement.Add(new XElement("Text", this.Text));

            return returnElement;
        }

        public override string ToString()
        {
            return string.Format("D {0} {1}: {2}", this.Severity.ToString().Substring(0, 1), this.DateTime.ToLocalTime().ToString(), this.Text);
        }
    }
}
